package com.smart.milvus.autoconfigure;

import lombok.extern.slf4j.Slf4j;
import com.smart.milvus.bean.BaseMilvusMapper;
import com.smart.milvus.bean.MilvusMapperImpl;

import java.lang.invoke.MethodHandles;
import java.lang.reflect.*;

@Slf4j
public class MilvusInvocationHandler implements InvocationHandler {
    private final MilvusMapperImpl milvusMapperImpl;
    private Class<? extends BaseMilvusMapper> mapperInterface;

    public MilvusInvocationHandler(MilvusMapperImpl milvusMapperImpl,
                                   Class<? extends BaseMilvusMapper> mapperInterface) {
        this.milvusMapperImpl = milvusMapperImpl;
        this.mapperInterface = mapperInterface;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] arguments) {
        try {
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, arguments);
            }
            if (method.isDefault()) {
                return invokeDefaultMethod(proxy, method, arguments);
            } else {
                return invokePlaintMethod(proxy, method, arguments);
            }
        } catch (Throwable t) {
            log.error("invoke service: {}, method: {}", mapperInterface.getName(), method.getName());
        }
        return null;
    }

    private Object invokePlaintMethod(Object proxy, Method method, Object[] arguments) {
        try {
            return method.invoke(milvusMapperImpl, arguments);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        }
    }

    private Object invokeDefaultMethod(Object proxy, Method method, Object[] args)
            throws Throwable {
        final Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
                .getDeclaredConstructor(Class.class, int.class);
        if (!constructor.isAccessible()) {
            constructor.setAccessible(true);
        }
        final Class<?> declaringClass = method.getDeclaringClass();
        return constructor.newInstance(declaringClass, MethodHandles.Lookup.PRIVATE)
                .unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);
    }

    /**
     * Backport of java.lang.reflect.Method#isDefault()
     */
    private boolean isDefaultMethod(Method method) {
        return ((method.getModifiers()
                & (Modifier.ABSTRACT | Modifier.PUBLIC | Modifier.STATIC)) == Modifier.PUBLIC)
                && method.getDeclaringClass().isInterface();
    }
}
